package com.cskaoyan.mall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.cskaoyan.mall.common.constant.RedisConst;
import com.cskaoyan.mall.product.client.SearchApiClient;
import com.cskaoyan.mall.product.dto.*;
import com.cskaoyan.mall.product.mapper.SkuInfoMapper;
import com.cskaoyan.mall.product.model.SkuInfo;
import com.cskaoyan.mall.product.service.*;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
 * @Author Pudding
 * @Date 2024/7/2 17:00
 */
@Slf4j
@Service
public class ProductDetailServiceImpl implements ProductDetailService {

    @Autowired
    SkuService skuService;
    @Autowired
    CategoryService categoryService;
    @Autowired
    SpuService spuService;
    @Autowired
    SalesAttributeService salesAttributeService;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    SearchApiClient searchApiClient;

    //1. 前端获取sku详情信息
    @Override
    public ProductDetailDTO getItemBySkuIdV1(Long skuId) {

        long startTime = System.currentTimeMillis();

        // 创建ProductDetailDTO对象永远存储从数据库中查询的信息
        ProductDetailDTO productDetailDTO = new ProductDetailDTO();

        //1.1 根据skuId获取sku商品基本信息(还需要包含sku商品的图片列表)
        SkuInfoDTO skuInfo = skuService.getSkuInfo(skuId);
        productDetailDTO.setSkuInfo(skuInfo);

        //1.2 根据skuId和spuId获取sku完整的销售属性值列表
        List<SpuSaleAttributeInfoDTO> spuSaleAttributeInfoDTOList = skuService.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
        productDetailDTO.setSpuSaleAttrList(spuSaleAttributeInfoDTOList);

        //1.3 查询销售属性值的组合（这个是sku_sale_attr_value表） 和 skuId的映射关系：目的为了sku的切换
        // 销售属性值的组合				  skuId
        // 冰岛幻境，8+128G    ----------- 15
        // 冰岛幻境，8+256G    ----------- 16
        // 幻夜星河，8+128G    ----------- 13
        // 幻夜星河，8+256G    ----------- 14
        // 3770|3773    ----------- 13
        // 3770|3774    ----------- 14
        Map<String, Long> skuValueIdsMap = spuService.getSkuValueIdsMap(skuInfo.getSpuId());
        //将map转换为JsonString
        //在Java中，把对象转换为JSON字符串，常用框架有三种：1.FastJson 2.Jackson 3.Gson
        String jsonString = JSON.toJSONString(skuValueIdsMap);
        productDetailDTO.setValuesSkuJson(jsonString);

        //1.4 查询sku的价格（为什么单独查价格呢？因为后续需要把除了价格以外的其他数据放入缓存，但是价格是用户敏感的数据，不放入缓存）
        BigDecimal skuPrice = skuService.getSkuPrice(skuId);
        productDetailDTO.setPrice(skuPrice);

        //1.5 查询sku的分类信息(包括一级分类、二级分类、三级分类)
        CategoryHierarchyDTO categoryViewByCategoryId = categoryService.getCategoryViewByCategoryId(skuInfo.getThirdLevelCategoryId());
        productDetailDTO.setCategoryHierarchy(categoryViewByCategoryId);

        //1.6 根据spuId获取sku商品的海报列表
        List<SpuPosterDTO> spuPosterBySpuId = spuService.findSpuPosterBySpuId(skuInfo.getSpuId());
        productDetailDTO.setSpuPosterList(spuPosterBySpuId);

        //1.7 根据skuId获取sku商品的平台属性集合（规格包装中，查询这个sku的平台属性和平台属性值，一个平台属性对应一个平台属性值）
        List<PlatformAttributeInfoDTO> platformAttributeInfoDTOList = skuService.getPlatformAttrInfoBySku(skuId);
        //将平台属性转化为规格数据
        List<SkuSpecification> skuSpecificationList = platformAttributeInfoDTOList.stream().map(baseAttrInfo -> {
            //1.7.1 创建封装规格数据的对象
            SkuSpecification skuSpecification = new SkuSpecification();
            //1.7.2 给对象赋值
            skuSpecification.setAttrName(baseAttrInfo.getAttrName());
            skuSpecification.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
            //1.7.3 返回该对象
            return skuSpecification;
        }).collect(Collectors.toList());
        productDetailDTO.setSkuAttrList(skuSpecificationList);

        long endTime = System.currentTimeMillis();
        log.info("商品详情页查询总时间：{}毫秒，开始时间：{}，结束时间：{}，skuId：{}", (endTime - startTime), startTime, endTime, skuId);


        return productDetailDTO;
    }


    //1. 获取sku详情信息，使用CompletableFuture进行多任务的异步编排
    @Override
    public ProductDetailDTO getItemBySkuIdV2(Long skuId) {

        long startTime = System.currentTimeMillis();

        // 创建ProductDetailDTO对象存储从数据库中查询的信息
        ProductDetailDTO productDetailDTO = new ProductDetailDTO();

        //在真正查询数据库之前，先判断一下这个元素在不在布隆过滤器中
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConst.SKU_BLOOM_FILTER);
        boolean isContains = bloomFilter.contains(skuId);

        // TODO 由于BloomFilter是把元素存储在本地内存中的
        // 那么在本地测试时候，由于会不停地重启服务
        // 所以，每次重启，就会导致布隆过滤器中的所有数据都会丢失，那么就需要重新上架
        // 针对这种情况
            // 方式一：在每次服务启动的时候，不仅仅只初始化布隆过滤器，还可以把数据库中所有已经上架的商品都查询出来，存入布隆过滤器
            // 方式二：可以在本地开发其他功能的时候，把布隆过滤器的校验逻辑先注释掉


        //如果不存在，则返回空对象，或抛出一个异常
        if (!isContains){
            return productDetailDTO;
        }


        //创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(10);

        //1.1 根据skuId获取sku商品基本信息(还需要包含sku商品的图片列表)
        CompletableFuture<SkuInfoDTO> cf1 = CompletableFuture.supplyAsync(() -> {
            SkuInfoDTO skuInfo = skuService.getSkuInfo(skuId);
            productDetailDTO.setSkuInfo(skuInfo);

            return skuInfo;
        }, pool);


        //1.2 根据skuId和spuId获取sku完整的销售属性值列表
        CompletableFuture<Void> cf5 = cf1.thenAcceptAsync(skuInfo -> {
            List<SpuSaleAttributeInfoDTO> spuSaleAttributeInfoDTOList = skuService.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
            productDetailDTO.setSpuSaleAttrList(spuSaleAttributeInfoDTOList);
        }, pool);


        //1.3 查询销售属性值的组合（这个是sku_sale_attr_value表） 和 skuId的映射关系：目的为了sku的切换
        CompletableFuture<Void> cf6 = cf1.thenAcceptAsync(skuInfo -> {
            Map<String, Long> skuValueIdsMap = spuService.getSkuValueIdsMap(skuInfo.getSpuId());
            //将map转换为JsonString
            //在Java中，把对象转换为JSON字符串，常用框架有三种：1.FastJson 2.Jackson 3.Gson
            String jsonString = JSON.toJSONString(skuValueIdsMap);
            productDetailDTO.setValuesSkuJson(jsonString);
        }, pool);

        //1.4 查询sku的价格（为什么单独查价格呢？因为后续需要把除了价格以外的其他数据放入缓存，但是价格是用户敏感的数据，不放入缓存）
        CompletableFuture<Void> cf2 = CompletableFuture.runAsync(() -> {
            BigDecimal skuPrice = skuService.getSkuPrice(skuId);
            productDetailDTO.setPrice(skuPrice);
        }, pool);

        //1.5 查询sku的分类信息(包括一级分类、二级分类、三级分类)
        CompletableFuture<Void> cf3 = cf1.thenAcceptAsync(skuInfo -> {
            CategoryHierarchyDTO categoryViewByCategoryId = categoryService.getCategoryViewByCategoryId(skuInfo.getThirdLevelCategoryId());
            productDetailDTO.setCategoryHierarchy(categoryViewByCategoryId);
        }, pool);


        //1.6 根据spuId获取sku商品的海报列表
        CompletableFuture<Void> cf4 = cf1.thenAcceptAsync(skuInfo -> {
            List<SpuPosterDTO> spuPosterBySpuId = spuService.findSpuPosterBySpuId(skuInfo.getSpuId());
            productDetailDTO.setSpuPosterList(spuPosterBySpuId);
        }, pool);


        //1.7 根据skuId获取sku商品的平台属性集合（规格包装中，查询这个sku的平台属性和平台属性值，一个平台属性对应一个平台属性值）
        CompletableFuture<Void> cf7 = CompletableFuture.runAsync(() -> {
            List<PlatformAttributeInfoDTO> platformAttributeInfoDTOList = skuService.getPlatformAttrInfoBySku(skuId);
            //将平台属性转化为规格数据
            List<SkuSpecification> skuSpecificationList = platformAttributeInfoDTOList.stream().map(baseAttrInfo -> {
                //1.7.1 创建封装规格数据的对象
                SkuSpecification skuSpecification = new SkuSpecification();
                //1.7.2 给对象赋值
                skuSpecification.setAttrName(baseAttrInfo.getAttrName());
                skuSpecification.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                //1.7.3 返回该对象
                return skuSpecification;
            }).collect(Collectors.toList());
            productDetailDTO.setSkuAttrList(skuSpecificationList);
        }, pool);

        // 等待所有的异步任务执行结束，再返回
        CompletableFuture.allOf(cf2, cf3, cf4, cf5, cf6, cf7).join();

        long endTime = System.currentTimeMillis();
        log.info("商品详情页查询总时间：{}毫秒，开始时间：{}，结束时间：{}，skuId：{}", (endTime - startTime), startTime, endTime, skuId);

        //远程调用搜索服务的接口，给对应的商品的热度 + 1
        searchApiClient.incrHotScore(skuId);

        return productDetailDTO;
    }
}
